﻿using Nes.Core.Buss;
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Text;
using System.Threading.Tasks;

namespace Nes.Core.APUS
{
    public class APU
    {
        public IBus CPUBus { set { Dmc.CPUBus = value; } }
        public ICPU CPU { set { Dmc.CPU = value; } }

        public Dmc Dmc { get; set; }
        public Triangle Triangle { get; set; }

        public Pulse Pulse1 { get; set; }
        public Pulse Pulse2 { get; set; }

        public Noise Noise { get; set; }

        private Boolean FrameInterruptFlag;
        private Int32 Mode;
        private Boolean IsIRQEnabled;
        private Int32 Clocks;
        private Int32 SampleCounter;
        private Int32 FrameCounter;
        private Int32 SampleRate;
        private Action<Double> onSample;

        public APU(Int32 sampleRate, Action<Double> onSample)
        {
            this.SampleRate = sampleRate;
            this.onSample = onSample;
            Pulse1 = new Pulse(1);
            Pulse2 = new Pulse(2);
            Triangle = new Triangle();
            Noise = new Noise();
            Dmc = new Dmc();
            FrameInterruptFlag = false;
            Mode = 0;
            IsIRQEnabled = true;
            Clocks = 0;
            SampleCounter = 0;
            FrameCounter = 0;
            InitTable();
        }
        private Double[] pulseTable;
        private Double[] tndTable;
        private void InitTable()
        {
            pulseTable = new Double[32];
            tndTable = new Double[204];
            for(var i = 0; i < 32; i++)
            {
                pulseTable[i] = 95.52 / (8128.0 / i + 100);
            }
            for (var i = 0; i < 204; i++)
            {
                tndTable[i] = 163.67 / (24329.0 / i + 100);
            }
        }

        public void Clock() 
        {
            Clocks++;
            if((Clocks & 1) > 0)
            {
                Pulse1.Clock();
                Pulse2.Clock();
                Noise.Clock();
            }
            Dmc.Clock();
            Triangle.Clock();
            var count = (Int32)(Clocks / (1789773.0 / SampleRate));
            if(count != SampleCounter)
            {
                SampleCounter = count;
                SampleOutput();
            }
            var frameCount = (Int32)(Clocks / (1789773.0 / 240));
            if(frameCount != SampleCounter)
            {
                FrameCounter = frameCount;
                ProcessFrameCounter();
            }

        }

        private void SampleOutput()
        {
            var pulseOut = 0.00752 * (Pulse1.Volume + Pulse2.Volume);
            var tndOut = 0.00851 * Triangle.Volume + 0.00494 * Noise.Volume + 0.00335 * Dmc.Volume;
            var v1 = pulseOut + tndOut;
            onSample?.Invoke(v1);
            //onSample?.Invoke(v1 * 0.98411 * 50000);
        }

        public Byte Read(Int32 addr) 
        {
            switch (addr)
            {
                case 0x4015:
                    // $4015 read	IF-D NT21	DMC interrupt (I), frame interrupt (F), DMC active (D), length counter > 0 (N/T/2/1)
                    Byte data = (Byte)(
                        (Pulse1.LengthCounter > 0 ? 1 : 0)
                        | ((Pulse2.LengthCounter > 0 ? 1 : 0) >> 1)
                        | ((Triangle.LengthCounter > 0 ? 1 : 0) >> 2)
                        | ((Noise.LengthCounter > 0 ? 1 : 0) >> 3)
                        | ((Dmc.BytesRemainingCounter > 0 ? 1 : 0) >> 4)
                        | (1 >> 5)
                        | ((FrameInterruptFlag ? 1 : 0) >> 6)
                        | ((Dmc.InterruptFlag ? 1 : 0) >> 7)
                        );
                    FrameInterruptFlag = false;
                    return data;
                default:
                    return 0;
            }
        }

        public void Write(Int32 addr, Byte data) 
        {  
            switch (addr) 
            {
                case 0x4000: 
                case 0x4001: 
                case 0x4002: 
                case 0x4003: // Pluse1
                    Pulse1.Write(addr - 0x4000, data);
                    break;
                case 0x4004:
                case 0x4005:
                case 0x4006:
                case 0x4007: // Pluse2
                    Pulse2.Write(addr - 0x4004, data);
                    break;
                case 0x4008:
                case 0x4009:
                case 0x400A:
                case 0x400B: // Triangle
                    Triangle.Write(addr - 0x4008, data);
                    break;
                case 0x400C:
                case 0x400D:
                case 0x400E:
                case 0x400F: // Noise
                    Noise.Write(addr - 0x400C, data);
                    break;
                case 0x4010:
                case 0x4011:
                case 0x4012:
                case 0x4013: // DMC
                    Dmc.Write(addr - 0x4010, data);
                    break;
                case 0x4015: // 状态寄存器
                    // $4015 write	---D NT21	Enable DMC (D), noise (N), triangle (T), and pulse channels (2/1)
                    Pulse1.Enable = (data & 0x1) > 0;
                    Pulse2.Enable = (data & 0x2) > 0;
                    Triangle.Enable = (data & 0x4) > 0;
                    Noise.Enable = (data & 0x8) > 0;
                    Dmc.Enable = (data & 0x10) > 0;
                    Dmc.InterruptFlag = false;
                    break;
                case 0x4017: //帧计数器
                    FrameCounter = 0;
                    // $4017   MI------   Mode(M, 0 = 4 - step, 1 = 5 - step), IRQ inhibit flag(I)
                    Mode = data >> 7;
                    IsIRQEnabled = !((data & 0x40) > 0);
                    break;
            }
        }

        /// <summary>
        /// mode 0:    mode 1:       function
        /// ---------  -----------  -----------------------------
        ///  ---f      -----        IRQ(if bit 6 is clear)
        ///  -l-l      -l--l        Length counter and sweep
        ///  eeee      eee-e        Envelope and linear counter
        /// </summary>
        private void ProcessFrameCounter() 
        {
            if(Mode == 0)
            {
                switch(FrameCounter % 4)
                {
                    case 0:
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 1:
                        ProcessLengthCounterAndSweep();
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 2: 
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 3:
                        if (IsIRQEnabled)
                        {
                            FrameInterruptFlag = true;
                            Dmc.CPU.IRQ();
                        }
                        ProcessLengthCounterAndSweep();
                        ProcessEnvelopeAndLinearCounter();
                        break;
                }
            }
            else
            {
                switch(FrameCounter % 5)
                {
                    case 0:
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 1:
                        ProcessLengthCounterAndSweep();
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 2:
                        ProcessEnvelopeAndLinearCounter();
                        break;
                    case 3: 
                        break;
                    case 4:
                        ProcessLengthCounterAndSweep();
                        ProcessEnvelopeAndLinearCounter();
                        break;
                }
            }
        }

        private void ProcessLengthCounterAndSweep()
        {
            Pulse1.ProcessLengthCounter();
            Pulse2.ProcessLengthCounter();
            Triangle.ProcessLengthCounter();
            Noise.ProcessLengthCounter();
            Pulse1.ProcessSweep();
            Pulse2.ProcessSweep();

        }

        private void ProcessEnvelopeAndLinearCounter()
        {
            Pulse1.ProcessEnvelope();
            Pulse2.ProcessEnvelope();
            Noise.ProcessEnvelope();
            Triangle.ProcessLinearlope();
        }
    }
}
